package com.demo.aop;

import com.demo.aop.advice.Advice;
import com.demo.aop.advisor.Advisor;
import com.demo.aop.pointcut.Pointcut;
import com.demo.bean.BeanDefinition;
import com.demo.bean.BeanFactory;
import com.demo.bean.DefaultBeanFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class CglibAopProxy implements AopProxy, MethodInterceptor {

    private ClassLoader classLoader;
    private Class[] interfaces;
    private List<Advisor> advisorList;
    private Object target;
    private Enhancer enhancer;
    private BeanFactory beanFactory;
    private String beanName;


    public CglibAopProxy(ClassLoader classLoader, Class[] interfaces, List<Advisor> advisorList, Object target,String beanName, BeanFactory beanFactory){
        this.classLoader = classLoader;
        this.interfaces = interfaces;
        this.advisorList = advisorList;
        this.target = target;
        this.beanName = beanName;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getProxy() throws Exception {
        return getProxy(this.classLoader);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) throws Exception {
        enhancer = new Enhancer();
        enhancer.setCallback(this);
        enhancer.setInterfaces(interfaces);
        enhancer.setSuperclass(target.getClass());
        Object proxy = null;
        try {
            proxy = enhancer.create();
        }catch (Exception e){

        }
        if(proxy==null){
            BeanDefinition beanDefinition = ((DefaultBeanFactory) beanFactory).getBeanDefinition(beanName);
            List<Object> constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
            proxy = enhancer.create(beanDefinition.getConstructor().getParameterTypes(), constructorArgumentValues==null?null:constructorArgumentValues.toArray());
        }
        return proxy;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        List<Advice> matchAdvice = getAllAdvice(method);
        InvocationHandleMethodChain invocationHandleMethodChain = new InvocationHandleMethodChain(matchAdvice,target,method,objects);
        return invocationHandleMethodChain.invoke();
    }

    private List<Advice> getAllAdvice(Method method) throws Exception {
        if(advisorList == null){
            return null;
        }
        List<Advice> adviceList = new ArrayList<>(advisorList.size());
        for (Advisor advisor : advisorList) {
            Pointcut pointcut = advisor.getPointcut();
            if(!pointcut.matchClass(target.getClass())){
                continue;
            }
            if(pointcut.matchMethod(target.getClass(),method)){
                adviceList.add(beanFactory.getBean(advisor.getAdviceBeanName(), Advice.class));
            }
        }
        return adviceList;
    }
}
